package xjtu.sad.zbsj.util.impl;

import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipFile;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import xjtu.sad.zbsj.common.Utils;
import xjtu.sad.zbsj.dao.labelTask.LabelTaskDao;
import xjtu.sad.zbsj.dao.util.UtilDao;
import xjtu.sad.zbsj.task.annotater.labelTask.TaskInfo;
import xjtu.sad.zbsj.template.Attribute;
import xjtu.sad.zbsj.template.AttributeValue;
import xjtu.sad.zbsj.template.Template;
import xjtu.sad.zbsj.template.TemplateService;
import xjtu.sad.zbsj.util.*;

import javax.annotation.Resource;
import java.io.*;
import java.util.*;

@Service
@Transactional
public class UtilServiceImpl implements UtilService {

    @Resource
    private UtilDao utilDao;

    @Resource
    private TemplateService templateService;

    @Resource
    private LabelTaskDao labelTaskDao;

    /**
     * 根据角色id查找当前角色可以访问的所有菜单
     *
     * @param roleId 角色id
     * @return 该角色可以访问的全部菜单
     */
    @Override
    public List<MenuItem> getMenuList(Integer roleId) {
        List<MenuItem> menuItemList = utilDao.getMenuItemList(roleId); // 获得所有地址列表
        System.out.println(menuItemList.size());
        // 根据父子关系构建返回菜单栏
        List<MenuItem> retMenuItemList = new ArrayList<>();
        Map<Integer, MenuItem> parentMenuItemMap = new HashMap<>();

        // 构建父菜单栏
        for (MenuItem menuItem : menuItemList) {
            if (menuItem.getParentMenuId() == null) {
                retMenuItemList.add(menuItem);
                parentMenuItemMap.put(menuItem.getId(), menuItem);
            }
        }

        // 构建子菜单栏
        for (MenuItem menuItem : menuItemList) {
            if (menuItem.getParentMenuId() != null) {
                MenuItem parentMenuItem = parentMenuItemMap.get(menuItem.getParentMenuId());
                parentMenuItem.setHasChildren(true);
                menuItem.setHasChildren(false);
                parentMenuItem.appendChildren(menuItem);
            }
        }

        return retMenuItemList;
    }

    /**
     * 生成响应任务的所有数据,将其转换成csv文件
     * <p>
     * 1.首先需要获得该任务的模板信息,然后将其属性的类型与属性名均构造成hash表的形式
     * 构造属性类型的hash表是为了判断某个属性是不是select,因为select的attrValue中存储的是value的id而非值,因此需要经过一次转换
     * 构造属性值的hash表是为了方便快速将valueId转换为value值
     * 2.然后需要查找该任务下面的所有标注信息,从t_mark表中找该task_id的所有标注,需要满足的条件是该任务不是异常并且状态为YTG(已通过)
     * 同时需要将标注情况表构造成一个hash表,是为了获取到具体的标注信息后插入到标注信息中
     * 再添加时需要检查该标注是不是select类型的,如果是则需要将其valueId转换为value存储
     * 3.然后根据任务名称和任务id构建保存的文件名称(为了防止重复),然后调用generateCsvFile函数生成csv文件
     * 4.最后需要将生成的文件名该数据插入到数据库对应的任务信息中,以便下次直接返回而无需再次生成文件
     *
     * @param taskInfo 任务id
     * @param path     文件生成保存的路径
     * @return 生成的文件的文件名称
     */
    @Override
    public String generateOutPutFile(TaskInfo taskInfo, String path) {
        // 获得模板信息
        Template template = templateService.getTemplateInfo(taskInfo.getTemplateId());
        System.out.println(template);

        // 获得属性类型对应图,用于查看该属性是不是选择框,如果是的话,需要转化存储的id为真实值
        Map<Integer, String> attrHashMap = new HashMap<>();

        // 获得属性值对应图,用于将选择框选择的值转换为真实值
        Map<Integer, Object> attrValueHashMap = new HashMap<>();

        for (Attribute attribute : template.getAttrs()) {
            attrHashMap.put(attribute.getAttrId(), attribute.getAttrType());
            for (AttributeValue attributeValue : attribute.getAttrValues()) {
                attrValueHashMap.put(attributeValue.getValueId(), attributeValue.getValueName());
            }
        }

        // 获取该任务的所有标注情况
        List<MarkInfo> markInfoList = utilDao.getMarkInfoList(taskInfo.getTaskId());

        // 构造hash表,用于一会插入数据
        Map<Integer, MarkInfo> markInfoHashMap = new HashMap<>();
        for (MarkInfo markInfo : markInfoList) {
            markInfoHashMap.put(markInfo.getMarkId(), markInfo);
        }

        // 获取任务的具体标注结果
        List<MarkDetail> markDetailList = utilDao.getMarkDetailList(markInfoList);
        System.out.println(markDetailList);

        // 将标注详情添加到具体的标注中
        for (MarkDetail markDetail : markDetailList) {
            if ("select".equals(attrHashMap.get(markDetail.getAttrNameId()))) {
                int valueId = Integer.parseInt(markDetail.getAttrValue());
                markDetail.setAttrValue((String) attrValueHashMap.get(valueId));
            }
            markInfoHashMap.get(markDetail.getMarkId()).addMarkDetail(markDetail);
        }
        System.out.println(markInfoList);

        String fileName = taskInfo.getTaskTitle() + "_" + taskInfo.getTaskId() + ".csv";
        String fullPath = path + fileName;
        generateOutPutCsvFile(markInfoList, fullPath, template.getAttrs());
        utilDao.saveFileName(taskInfo.getTaskId(), fileName);
        return fileName;
    }

    /**
     * 生成csv文件
     * <p>
     * 1.先对传入的数组进行排序
     * 2.然后需要判断文件是否存在,如果不存在则需要创建文件
     * 3.然后构造表格的表头,主要有文件名以及属性名信息(需要从模板信息中拼接)
     * 4.构造表格内容,第一列对应的是文件名,后面是具体的标注内容,在这里进行了一个处理,不论单选,多选还是input都放在一个单元格中
     * 对于单选和input直接就是最后结果,对于多选在这里使用了'&&'分割符进行了分割,这需要提前与标注方约定好
     *
     * @param markInfoList 标注信息
     * @param fullPath     文件路径
     * @param attrs        属性列表
     */
    private static void generateOutPutCsvFile(List<MarkInfo> markInfoList, String fullPath, List<Attribute> attrs) {
        File outFile = new File(fullPath);
        File parentFile = outFile.getParentFile();
        System.out.println(fullPath);
        markInfoList.sort(Comparator.comparingInt(MarkInfo::getContentId));

        try {
            // 如果该文件不存在则需要创建文件
            if (!parentFile.exists()) {
                parentFile.mkdirs();
            }
            if (!outFile.exists()) {
                outFile.createNewFile();
            }
            BufferedWriter writer = new BufferedWriter(new FileWriter(outFile));
            StringBuilder title = new StringBuilder("文件名,");
            // 该map存储属性导出的顺序
            Map<Integer, Integer> attrOrderMap = new HashMap<>();
            int attrLength = attrs.size();
            // 构造输出表格的title
            for (int i = 0; i < attrLength; i++) {
                Attribute attribute = attrs.get(i);
                attrOrderMap.put(attribute.getAttrId(), i);
                title.append(attribute.getAttrName());
                if (i != attrLength - 1) {
                    title.append(",");
                } else {
                    title.append("\n");
                }
            }
            writer.write(title.toString());

            String[] attrValueList = new String[attrLength];
            for (MarkInfo markInfo : markInfoList) {
                Arrays.fill(attrValueList, null);
                StringBuilder sb = new StringBuilder();
                sb.append(markInfo.getFileName()).append(",");

                for (MarkDetail markDetail : markInfo.getMarkDetailList()) {
                    int index = attrOrderMap.get(markDetail.getAttrNameId());
                    // 主要是为了区分单选与多选,多选题约定不同答案通过'&&'符号分割
                    if (attrValueList[index] == null) {
                        attrValueList[index] = markDetail.getAttrValue();
                    } else {
                        attrValueList[index] += "&&" + markDetail.getAttrValue();
                    }
                }
                sb.append(String.join(",", attrValueList)).append("\n");
                System.out.println(sb);
                writer.write(sb.toString());
            }
            writer.close();

        } catch (
                Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * zip解压
     *
     * @param srcFile     zip源文件
     * @param destDirPath 解压后的目标文件夹
     * @param taskId      任务id
     */
    @Override
    public void unZipFiles(File srcFile, String destDirPath, Integer taskId) {
        List<FileDetail> fileList = new ArrayList<>();
        // 判断源文件是否存在
        if (!srcFile.exists()) {
            throw new RuntimeException(srcFile.getPath() + "所指文件不存在");
        }
        // 开始解压
        ZipFile zipFile = null;
        try {
            zipFile = new ZipFile(srcFile);
            Enumeration<?> entries = zipFile.getEntries();
            while (entries.hasMoreElements()) {
                ZipEntry entry = (ZipEntry) entries.nextElement();
                System.out.println("解压" + entry.getName());
                if (entry.isDirectory()) {
                    // 如果是文件夹，就创建个文件夹
                    String dirPath = destDirPath + "/" + entry.getName();
                    File dir = new File(dirPath);
                    dir.mkdirs();
                } else {
                    // 如果是文件，就先创建一个文件，然后用io流把内容copy过去
                    // 使用当前时间作为唯一表示字
                    String suffix = getSuffix(entry.getName());
                    String newFileName = Utils.imgNameGenerator() + "." + suffix;
                    File targetFile = new File(destDirPath + "/" + newFileName);
                    FileDetail fileDetail = new FileDetail(targetFile, entry.getName(), suffix);
                    fileList.add(fileDetail);
                    // 保证这个文件的父文件夹必须要存在
                    if (!targetFile.getParentFile().exists()) {
                        targetFile.getParentFile().mkdirs();
                    }
                    targetFile.createNewFile();
                    // 将压缩文件内容写入到这个文件中
                    InputStream is = zipFile.getInputStream(entry);
                    FileOutputStream fos = new FileOutputStream(targetFile);
                    int len;
                    byte[] buf = new byte[1024];
                    while ((len = is.read(buf)) != -1) {
                        fos.write(buf, 0, len);
                    }
                    // 关流顺序，先打开的后关闭
                    fos.close();
                    is.close();
                }
            }
            getFileDetail(fileList, taskId); // 获取文件详细信息
            utilDao.insertFileInfo(fileList);// 插入文件信息
            utilDao.insertTaskContent(fileList);// 插入任务信息
            TaskInfo taskInfo = labelTaskDao.getTaskInfo(taskId);
            int count = taskInfo.getContentCount() == null ? 0 : taskInfo.getContentCount();
            utilDao.updateTaskContentCount(taskId, count + fileList.size());
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("unzip error from ZipUtils", e);
        } finally {
            if (zipFile != null) {
                try {
                    zipFile.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 获得文件名的后缀
     *
     * @param fileName 文件名
     * @return 文件名的后缀
     */
    private String getSuffix(String fileName) {
        String[] temp = fileName.split("\\.");
        return temp[temp.length - 1];
    }

    /**
     * 获取文件详情
     *
     * @param fileList 文件列表
     * @param taskId   任务id
     */
    private void getFileDetail(List<FileDetail> fileList, Integer taskId) {
        for (FileDetail fileDetail : fileList) {
            File file = fileDetail.getFile();
            fileDetail.setFileUrl(file.getName());// 存储的名称与实际名称不同,导出时需要使用实际名称
            fileDetail.setFileSize(file.length() + "");// 文件大小
            fileDetail.setTaskId(taskId);
            if ("txt".equals(fileDetail.getFileType())) {
                // 如果是文本类型,则需要获得fileContent
                String fileContent = getFileContent(fileDetail.getFile());
                fileDetail.setFileContent(fileContent);
            }
        }
    }

    /**
     * 获取传入文件的内容
     *
     * @param file 文件对象
     * @return 文件内容
     */
    private String getFileContent(File file) {
        BufferedReader reader = null;
        StringBuilder sbf = new StringBuilder();
        try {
            reader = new BufferedReader(new FileReader(file));
            String tempStr;
            while ((tempStr = reader.readLine()) != null) {
                sbf.append(tempStr);
            }
            reader.close();
            return sbf.toString();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
        }
        return sbf.toString();
    }


    /**
     * @param filePath 临时文件的删除
     *                 删除文件夹里面子目录
     *                 再删除文件夹
     */
    @Override
    public void deleteFiles(String filePath) {
        File file = new File(filePath);
        if ((!file.exists()) || (!file.isDirectory())) {
            System.out.println("file not exist");
            return;
        }
        String[] tempList = file.list();
        File temp;
        for (String s : tempList) {
            if (filePath.endsWith(File.separator)) {
                temp = new File(filePath + s);
            } else {
                temp = new File(filePath + File.separator + s);
            }
            if (temp.isFile()) {
                temp.delete();
            }
            if (temp.isDirectory()) {
                this.deleteFiles(filePath + "/" + s);
            }
        }
        // 空文件的删除
        file.delete();
    }


}
